`timescale 1ns / 1ps

`include "data_width.vh"

module rd_edge_info #(parameter
    DST_ID_DWIDTH       = `DST_ID_DWIDTH,
    EDGE_OFF_DWIDTH     = `EDGE_OFF_DWIDTH,
    MEM_AWIDTH          = `MEM_AWIDTH,
    VERTEX_MASK_WIDTH   = `VERTEX_MASK_WIDTH,
    VERTEX_PIPE_NUM     = `VERTEX_PIPE_NUM
    ) (
        input                                                   clk,
        input                                                   front_rst,
        input [DST_ID_DWIDTH * VERTEX_PIPE_NUM - 1 : 0]         front_dst_id,
        input [EDGE_OFF_DWIDTH * VERTEX_PIPE_NUM - 1 : 0]       front_edge_off_l,
        input [EDGE_OFF_DWIDTH * VERTEX_PIPE_NUM - 1 : 0]       front_edge_off_r,
        input [VERTEX_PIPE_NUM - 1 : 0]                         front_dst_data_valid,
        input                                                   mem_full,
        input                                                   addr_rev_rd_plus_valid,
        input                                                   back_stage_vertex_full,

        input [MEM_AWIDTH - 1 : 0]                              front_edge_info_addr_ed,
        input [EDGE_OFF_DWIDTH - 1 : 0]                         front_edge_off_ed,
        input [EDGE_OFF_DWIDTH - VERTEX_MASK_WIDTH - 1 : 0]     front_mem_edge_ed,
        input                                                   front_para_valid,

        output                                                  rst,

        output                                                  buffer_full_vertex,
        output [MEM_AWIDTH - 1 : 0]                             rd_edge_info_addr,
        output                                                  rd_edge_info_valid,
        output                                                  finish_read,
        output [DST_ID_DWIDTH * VERTEX_PIPE_NUM - 1 : 0]        dst_id,
        output [VERTEX_MASK_WIDTH * VERTEX_PIPE_NUM - 1 : 0]    edge_info_mask_l,
        output [VERTEX_MASK_WIDTH * VERTEX_PIPE_NUM - 1 : 0]    edge_info_mask_r,
        output [VERTEX_PIPE_NUM - 1 : 0]                        dst_data_valid);
    
    wire [EDGE_OFF_DWIDTH - VERTEX_MASK_WIDTH - 1 : 0] now_mem_edge;
    wire addr_buffer_empty, addr_buffer_full, addr_buffer_rev_full;
    wire [VERTEX_PIPE_NUM - 1 : 0] dst_buffer_empty, dst_buffer_full;
    wire [EDGE_OFF_DWIDTH * VERTEX_PIPE_NUM - 1 : 0] top_edge_off_r;

    rd_edge_info_para_trans P (
        .clk(clk), .front_rst(front_rst),

        .rst(rst));

    rd_edge_info_edge E0 (
        .clk                        (clk),
        .rst                        (front_rst),
        .mem_full                   (mem_full),
        .addr_rev_rd_plus_valid     (addr_rev_rd_plus_valid),

        .front_edge_info_addr_ed    (front_edge_info_addr_ed),
        .front_para_valid           (front_para_valid),

        .buffer_empty               (addr_buffer_empty),
        .buffer_full                (addr_buffer_full),
        .buffer_rev_full            (addr_buffer_rev_full),
        .rd_edge_info_addr          (rd_edge_info_addr),
        .rd_edge_info_valid         (rd_edge_info_valid));
    
    generate
        genvar i;
        for (i = 0; i < VERTEX_PIPE_NUM; i = i + 1) begin : M5_BLOCK_1
            rd_edge_info_vertex_single V (
                .clk(clk), .rst(front_rst),
                .now_mem_edge(now_mem_edge), // 迭代轮次
                .front_dst_id(front_dst_id[(i + 1) * DST_ID_DWIDTH - 1 : i * DST_ID_DWIDTH]),
                .front_edge_off_l(front_edge_off_l[(i + 1) * EDGE_OFF_DWIDTH - 1 : i * EDGE_OFF_DWIDTH]),
                .front_edge_off_r(front_edge_off_r[(i + 1) * EDGE_OFF_DWIDTH - 1 : i * EDGE_OFF_DWIDTH]),
                .front_dst_data_valid(front_dst_data_valid[i]),
                .back_stage_vertex_full(back_stage_vertex_full),

                .buffer_empty(dst_buffer_empty[i]), .buffer_full(dst_buffer_full[i]),
                .top_edge_off_r(top_edge_off_r[(i + 1) * EDGE_OFF_DWIDTH - 1 : i * EDGE_OFF_DWIDTH]),
                .dst_id(dst_id[(i + 1) * DST_ID_DWIDTH - 1 : i * DST_ID_DWIDTH]),
                .edge_info_mask_l(edge_info_mask_l[(i + 1) * VERTEX_MASK_WIDTH - 1 : i * VERTEX_MASK_WIDTH]),
                .edge_info_mask_r(edge_info_mask_r[(i + 1) * VERTEX_MASK_WIDTH - 1 : i * VERTEX_MASK_WIDTH]),
                .dst_data_valid(dst_data_valid[i]));
        end
    endgenerate

    rd_edge_info_vertex_control C0 (
        .clk                    (clk),
        .rst                    (front_rst),
        .buffer_empty_all       (dst_buffer_empty),
        .top_edge_off_r_all     (top_edge_off_r),
        .back_stage_vertex_full (back_stage_vertex_full),

        .front_edge_off_ed      (front_edge_off_ed),
        .front_mem_edge_ed      (front_mem_edge_ed),
        .front_para_valid       (front_para_valid),

        .now_mem_edge           (now_mem_edge),
        .finish_read            (finish_read));

    assign buffer_full_vertex = |dst_buffer_full;

endmodule

module rd_edge_info_para_trans (
    input clk,
    input front_rst,

    output reg rst);

    always @ (posedge clk) begin
        rst <= front_rst;
    end

endmodule

module rd_edge_info_edge #(parameter
    MEM_AWIDTH = `MEM_AWIDTH,
    PIPE_BUFFER_SIZE = `PIPE_BUFFER_SIZE, PIPE_AM_LEVEL = `PIPE_AM_LEVEL, PIPE_BUFFER_PTR_WIDTH = `PIPE_BUFFER_PTR_WIDTH,
    EDGE_INFO_ADDR_ST = `EDGE_INFO_ADDR_ST
    ) (
        input                       clk,
        input                       rst,
        input                       mem_full,
        input                       addr_rev_rd_plus_valid,

        input [MEM_AWIDTH - 1 : 0]  front_edge_info_addr_ed,
        input                       front_para_valid,

        output                      buffer_empty,
        output                      buffer_full,
        output                      buffer_rev_full,
        output [MEM_AWIDTH - 1 : 0] rd_edge_info_addr,
        output                      rd_edge_info_valid);
    
    reg  [PIPE_BUFFER_PTR_WIDTH - 1 : 0]    addr_rev_wr_ptr;
    reg  [PIPE_BUFFER_PTR_WIDTH - 1 : 0]    addr_rev_rd_ptr;
    reg  [MEM_AWIDTH - 1 : 0]               now_edge_info_addr;
    wire [PIPE_BUFFER_PTR_WIDTH - 1 : 0]    gap_addr_rev_ptr;

    wire wr_rst_busy, rd_rst_busy;

    always @ (posedge clk) begin
        if (rst) begin
            now_edge_info_addr <= EDGE_INFO_ADDR_ST;
        end
        else begin
            if (!buffer_full && !wr_rst_busy && front_para_valid) begin
                if (now_edge_info_addr == front_edge_info_addr_ed) begin
                    now_edge_info_addr <= EDGE_INFO_ADDR_ST;
                end
                else begin
                    now_edge_info_addr <= now_edge_info_addr + 32 * 4;
                end
            end
        end
    end

    always @ (posedge clk) begin
        if (rst) begin
            addr_rev_wr_ptr <= 0;
            addr_rev_rd_ptr <= 0;
        end
        else begin
            if (!(mem_full || buffer_empty || buffer_rev_full)) begin
                addr_rev_wr_ptr <= addr_rev_wr_ptr + 1;
            end
            if (addr_rev_rd_plus_valid) begin
                addr_rev_rd_ptr <= addr_rev_rd_ptr + 1;
            end
        end
    end

    assign gap_addr_rev_ptr = addr_rev_wr_ptr - addr_rev_rd_ptr;
    assign buffer_rev_full = (gap_addr_rev_ptr >= PIPE_AM_LEVEL) || rst;

    addr_32bit_fifo ADDR_FIFO (
        .clk(clk), .srst(rst),
        .din(now_edge_info_addr), .wr_en(!buffer_full && !wr_rst_busy && front_para_valid), .rd_en(!(mem_full || buffer_rev_full)),
        

        .dout(rd_edge_info_addr), .valid(rd_edge_info_valid),
        .empty(buffer_empty), .prog_full(buffer_full),
        .wr_rst_busy(wr_rst_busy), .rd_rst_busy(rd_rst_busy));

endmodule

module rd_edge_info_vertex_control #(parameter
    EDGE_OFF_DWIDTH     = `EDGE_OFF_DWIDTH,
    VERTEX_PIPE_NUM     = `VERTEX_PIPE_NUM,
    VERTEX_MASK_WIDTH   = `VERTEX_MASK_WIDTH,
    MEM_EDGE_ST         = `MEM_EDGE_ST
    ) (
        input                                                       clk,
        input                                                       rst,
        input [VERTEX_PIPE_NUM - 1 : 0]                             buffer_empty_all,
        input [EDGE_OFF_DWIDTH * VERTEX_PIPE_NUM - 1 : 0]           top_edge_off_r_all,
        input                                                       back_stage_vertex_full,

        input [EDGE_OFF_DWIDTH - VERTEX_MASK_WIDTH - 1 : 0]         front_mem_edge_ed,
        input [EDGE_OFF_DWIDTH - 1 : 0]                             front_edge_off_ed,
        input                                                       front_para_valid,

        output reg [EDGE_OFF_DWIDTH - VERTEX_MASK_WIDTH - 1 : 0]    now_mem_edge,
        output reg                                                  finish_read);
    
    wire [VERTEX_PIPE_NUM - 1 : 0] flag;
    wire [7 : 0] flag1;
    wire [3 : 0] flag2;

    generate
        genvar i;
        for (i = 0; i < VERTEX_PIPE_NUM; i = i + 1) begin
            assign flag[i] = (!buffer_empty_all[i]) && (((top_edge_off_r_all[(i + 1) * EDGE_OFF_DWIDTH - 1 : i * EDGE_OFF_DWIDTH + 5]) != now_mem_edge) || (top_edge_off_r_all[i * EDGE_OFF_DWIDTH + 4 : i * EDGE_OFF_DWIDTH] == 31) || (top_edge_off_r_all[(i + 1) * EDGE_OFF_DWIDTH - 1 : i * EDGE_OFF_DWIDTH] == front_edge_off_ed));
        end
        for (i = 0; i < 8; i = i + 1) begin
            assign flag1[i] = flag[i] || flag[i + 8];
        end
        for (i = 0; i < 4; i = i + 1) begin
            assign flag2[i] = flag1[i] || flag1[i + 4];
        end
    endgenerate

    always @ (posedge clk) begin
        if (rst) begin
            now_mem_edge <= MEM_EDGE_ST;
            finish_read <= 1'b0;
        end
        else begin
            if (!back_stage_vertex_full && (|flag2) && front_para_valid) begin
                if (now_mem_edge == front_mem_edge_ed) begin
                    now_mem_edge <= MEM_EDGE_ST;
                end
                else begin
                    now_mem_edge <= now_mem_edge + 1;
                end
                finish_read <= 1'b1;
            end
            else begin
                finish_read <= 1'b0;
            end
        end
    end

endmodule

module rd_edge_info_vertex_single #(parameter
    EDGE_OFF_DWIDTH = `EDGE_OFF_DWIDTH, DST_ID_DWIDTH = `DST_ID_DWIDTH,
    VERTEX_MASK_WIDTH = `VERTEX_MASK_WIDTH,
    PIPE_BUFFER_SIZE = `PIPE_BUFFER_SIZE, PIPE_AM_LEVEL = `PIPE_AM_LEVEL, PIPE_BUFFER_PTR_WIDTH = `PIPE_BUFFER_PTR_WIDTH
    ) (
        input                                               clk,
        input                                               rst,
        input [EDGE_OFF_DWIDTH - VERTEX_MASK_WIDTH - 1 : 0] now_mem_edge,
        input [DST_ID_DWIDTH - 1 : 0]                       front_dst_id,
        input [EDGE_OFF_DWIDTH - 1 : 0]                     front_edge_off_l,
        input [EDGE_OFF_DWIDTH - 1 : 0]                     front_edge_off_r,
        input                                               front_dst_data_valid,
        input                                               back_stage_vertex_full,

        output                                              buffer_empty,
        output                                              buffer_full,
        output [EDGE_OFF_DWIDTH - 1 : 0]                    top_edge_off_r,
        output reg [DST_ID_DWIDTH - 1 : 0]                  dst_id,
        output reg [VERTEX_MASK_WIDTH - 1 : 0]              edge_info_mask_l,
        output reg [VERTEX_MASK_WIDTH - 1 : 0]              edge_info_mask_r,
        output reg                                          dst_data_valid);
    
    wire [DST_ID_DWIDTH - 1 : 0]                        top_dst_id;
    wire [EDGE_OFF_DWIDTH - 1 : 0]                      top_edge_off_l;
    wire [EDGE_OFF_DWIDTH - VERTEX_MASK_WIDTH - 1 : 0]  tmp_edge_off_l;
    wire [EDGE_OFF_DWIDTH - VERTEX_MASK_WIDTH - 1 : 0]  tmp_edge_off_r;

    wire read_next;

    assign read_next = (now_mem_edge == tmp_edge_off_r) && !buffer_empty && !back_stage_vertex_full;

    dst_id_fifo_fall_through df (
        .clk(clk), .srst(rst),
        .din(front_dst_id),
        .wr_en(front_dst_data_valid), .rd_en(read_next),

        .dout(top_dst_id),
        .empty(buffer_empty), .prog_full(buffer_full));

    edge_off_fifo_fall_through lf (
        .clk(clk), .srst(rst),
        .din(front_edge_off_l),
        .wr_en(front_dst_data_valid), .rd_en(read_next),

        .dout(top_edge_off_l));

    edge_off_fifo_fall_through rf (
        .clk(clk), .srst(rst),
        .din(front_edge_off_r),
        .wr_en(front_dst_data_valid), .rd_en(read_next),

        .dout(top_edge_off_r));

    assign tmp_edge_off_l = top_edge_off_l[EDGE_OFF_DWIDTH - 1 : VERTEX_MASK_WIDTH];
    assign tmp_edge_off_r = top_edge_off_r[EDGE_OFF_DWIDTH - 1 : VERTEX_MASK_WIDTH];

    // output
    always @ (posedge clk) begin
        if (rst) begin
            dst_id           <= 0;
            edge_info_mask_l <= 0;
            edge_info_mask_r <= 0;
            dst_data_valid   <= 1'b0;
        end
        else begin
            if (!(buffer_empty || back_stage_vertex_full)) begin
                if (now_mem_edge >= tmp_edge_off_l && now_mem_edge <= tmp_edge_off_r) begin
                    dst_id          <= top_dst_id;
                    dst_data_valid  <= 1'b1;
                end
                else begin
                    dst_id          <= 0;
                    dst_data_valid  <= 1'b0;
                end
                if (now_mem_edge == tmp_edge_off_l) begin
                    edge_info_mask_l <= top_edge_off_l[VERTEX_MASK_WIDTH - 1 : 0];
                end
                else begin
                    edge_info_mask_l <= 0;
                end
                if (now_mem_edge == tmp_edge_off_r) begin
                    edge_info_mask_r <= top_edge_off_r[VERTEX_MASK_WIDTH - 1 : 0];
                end
                else begin
                    edge_info_mask_r <= {VERTEX_MASK_WIDTH{1'b1}};
                end
            end
            else begin
                dst_id              <= 0;
                edge_info_mask_l    <= 0;
                edge_info_mask_r    <= {VERTEX_MASK_WIDTH{1'b1}};
                dst_data_valid      <= 1'b0;
            end
        end
    end

endmodule